﻿namespace AgileDashboard.AppCode
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Configuration;
    using System.Diagnostics;
    using System.Drawing;
    using System.Linq;
    using System.Net;
    using System.Reflection;
    using System.Text.RegularExpressions;
    using System.Web;
    using System.Web.Caching;
    using System.Xml;
    using System.Xml.Linq;
    using System.Xml.XPath;

    using log4net;

    public class Util
    {
        /// <summary>
        /// The logger for this type.
        /// </summary>
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Compiled regular expression for performance. Matches everything but the BR tag
        /// </summary>
        private static readonly Regex HtmlRegex = new Regex(@"<\/?(?!br)[^/>]*>", RegexOptions.Compiled);

        /// <summary>
        /// The document to x document.
        /// </summary>
        /// <param name="doc">
        /// The doc.
        /// </param>
        /// <returns>
        /// The <see cref="XDocument"/>.
        /// </returns>
        public static XDocument DocumentToXDocument(XmlDocument doc)
        {
            return XDocument.Parse(doc.OuterXml);
        }

        /// <summary>
        /// Retrieve a value from an Xml document for a given generic type
        /// </summary>
        /// <typeparam name="T">The type to be returned</typeparam>
        /// <param name="attributeName">the name of the attribute to query for and return</param>
        /// <param name="element">the xml element in which to search</param>
        /// <returns>A typed object representing the value being searched for</returns>
        public static T GetValue<T>(string attributeName, XElement element)
        {
            T retVal = default(T);
            if (element == null)
            {
                throw new ArgumentNullException("element");
            }

            var innerElem = element.XPathSelectElement(string.Format("./Attribute[@name=\"{0}\"]", attributeName));
            if (innerElem == null || innerElem.Value == null)
            {
                throw new ApplicationException(string.Format("Could not load {0} attribute from XElement. Attribute or value is null.", attributeName));
            }

            if (innerElem.Value.Length > 1)
            {
                if (TryParse(innerElem.Value, out retVal))
                {
                    return retVal;
                }
            }

            return retVal;
        }


        public static bool TryParse<T>(string s, out T value)
        {
            TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
            try
            {
                if (converter != null)
                {
                    value = (T)converter.ConvertFromString(s);
                    return true;
                }

                value = default(T);
                return false;
            }
            catch
            {
                value = default(T);
                return false;
            }
        }

        /// <summary>
        /// Scales an html hex color value's luminance based on a specified value
        /// </summary>
        /// <param name="sourceColor">An HTML hex color string (may include the "#" character)</param>
        /// <param name="scaleFactor">A scale factor (EG: .25)</param>
        /// <returns>An HTML hex color string (with the #)</returns>
        public static string ScaleColor(string sourceColor, double scaleFactor)
        {
            Color col = ColorTranslator.FromHtml(sourceColor);
            int r = Math.Min(Convert.ToInt32(Math.Round(Convert.ToInt32(col.R) * scaleFactor)), 255); // scale the 8-bit red color value, then do a little bounds checking
            int g = Math.Min(Convert.ToInt32(Math.Round(Convert.ToInt32(col.G) * scaleFactor)), 255); // scale the 8-bit green color value, then do a little bounds checking
            int b = Math.Min(Convert.ToInt32(Math.Round(Convert.ToInt32(col.B) * scaleFactor)), 255); // scale the 8-bit blue color value, then do a little bounds checking

            return string.Format("#{0}{1}{2}", r.ToString("X2"), g.ToString("X2"), b.ToString("X2"));
        }

        /// <summary>
        /// Returns a HEX color value for the name of the entity specified
        /// </summary>
        /// <param name="entityValue">A string value that is used to pick a color for</param>
        /// <returns>A hex string representing a color</returns>
        public static string FuzzyColorPicker(string entityValue)
        {
            return FuzzyColorPicker(entityValue, HttpContext.Current.Server.MapPath(@"~/App_Data/SafeColors.xml"));
        }

        /// <summary>
        /// Returns a HEX color value for the name of the team specified
        /// </summary>
        /// <param name="entityValue">Name of the Version One team to pick a color for</param>
        /// <param name="colorFileLocation">A location for the xml file containing the colors to choose from</param>
        /// <returns>A hex string representing a color</returns>
        public static string FuzzyColorPicker(string entityValue, string colorFileLocation)
        {
            SortedDictionary<string, string> safeColorDictionary = GetSafeColorDictionary(colorFileLocation);

            KeyValuePair<string, string> closestMatch = safeColorDictionary.First(); // default the return to the first color in the list for safety

            int? lowestDistance = null;
            foreach (KeyValuePair<string, string> i in safeColorDictionary)
            {
                int distance = ComputeLevenshteinDistance(i.Key, entityValue + entityValue); // add the team name twice in order to generate more unique values
                Debug.WriteLine(string.Format("Distance from {0} to {1} is {2}", i.Key, entityValue, distance));
                if (!lowestDistance.HasValue)
                {
                    lowestDistance = distance;
                    closestMatch = i;
                }
                else if (distance < lowestDistance)
                {
                    lowestDistance = distance;
                    closestMatch = i;
                    Debug.WriteLine("NEW LOWEST DISTANCE!");
                }
            }

            return closestMatch.Value;
        }

        /// <summary>
        /// Retrieves a connection string by name.
        /// </summary>
        /// <param name="name">Name of the connection string element to retrieve</param>
        /// <returns>The value of the connection string. Returns null if the name is not found.</returns>
        internal static string GetConnectionStringByName(string name)
        {
            // Assume failure.
            string returnValue = null;

            // Look for the name in the connectionStrings section.
            var settings = ConfigurationManager.ConnectionStrings[name];

            // If found, return the connection string.
            if (settings != null)
                returnValue = settings.ConnectionString;

            return returnValue;
        }

        /// <summary>
        /// Compute the distance between two strings. Code borrowed from: http://www.dotnetperls.com/levenshtein
        /// </summary>
        /// <param name="s">
        /// The first string
        /// </param>
        /// <param name="t">
        /// The second string
        /// </param>
        /// <returns>
        /// The computed levenshtein distance between the two strings
        /// </returns>
        public static int ComputeLevenshteinDistance(string s, string t)
        {
            int n = s.Length;
            int m = t.Length;
            int[,] d = new int[n + 1, m + 1];

            // Step 1
            if (n == 0)
            {
                return m;
            }

            if (m == 0)
            {
                return n;
            }

            // Step 2
            for (int i = 0; i <= n; d[i, 0] = i++)
            {
            }

            for (int j = 0; j <= m; d[0, j] = j++)
            {
            }

            // Step 3
            for (int i = 1; i <= n; i++)
            {
                // Step 4
                for (int j = 1; j <= m; j++)
                {
                    // Step 5
                    int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;

                    // Step 6
                    d[i, j] = Math.Min(
                        Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                        d[i - 1, j - 1] + cost);
                }
            }

            // Step 7
            return d[n, m];
        }
        /// <summary>
        /// Remove HTML from string with compiled Regex.
        /// </summary>
        /// <param name="source">
        /// The source text.
        /// </param>
        /// <returns>
        /// The <see cref="string"/> that has been cleansed.
        /// </returns>
        public static string StripTags(string source)
        {
            return source != null ? HtmlRegex.Replace(source, string.Empty) : null;
        }

        /// <summary>
        /// Gets the safe color dictionary.
        /// </summary>
        /// <param name="colorFileLocation">
        /// The color file location.
        /// </param>
        /// <returns>
        /// The <see cref="SortedDictionary"/>.
        /// </returns>
        private static SortedDictionary<string, string> GetSafeColorDictionary(string colorFileLocation)
        {
            const string CacheKey = "GetSafeColorDictionary";

            var safeColorDictionary = HttpRuntime.Cache[CacheKey] as SortedDictionary<string, string>;

            if (safeColorDictionary == null)
            {
                Debug.WriteLine(string.Format("Cache miss for key {0}", CacheKey));
                if (Log.IsDebugEnabled)
                {
                    Log.DebugFormat("Cache miss for key {0}", CacheKey);
                }

                XElement colors = XElement.Load(colorFileLocation);

                safeColorDictionary = new SortedDictionary<string, string>();

                foreach (XElement color in colors.Elements("color"))
                {
                    var xElement = color.Element("name");
                    if (xElement != null && !safeColorDictionary.ContainsKey(xElement.Value))
                    {
                        var element = color.Element("hexValue");
                        if (element != null)
                        {
                            safeColorDictionary.Add(xElement.Value, element.Value);
                        }
                    }
                }

                HttpRuntime.Cache.Insert(CacheKey, safeColorDictionary, new CacheDependency(colorFileLocation), Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
            }
            else
            {
                Debug.WriteLine(string.Format("Cache hit for key {0}", CacheKey));
                if (Log.IsDebugEnabled)
                {
                    Log.DebugFormat("Cache hit for key {0}", CacheKey);
                }
            }

            return safeColorDictionary;
        }
    }
}
